/*
   Copyright (C) 2008 Yen-Ju Chen <yjchenx gmail>

   This application is free software; you can redistribute it and/or 
   modify it under the terms of the MIT license. See COPYING.

*/

#import "GNUstep.h"
#import "ICCollectionManager.h"

#define iClonesPathComponent (@"iClones")
#define iClonesSequencesPathComponent (@"Sequences")
#define iClonesSequencePathExtension (@"fasta")

static ICCollectionManager *sharedInstance = nil;

@interface ICCollectionManager (Private)
- (NSString *) uniqueIdentifier;
- (NSString *) pathToDatabase;
- (NSString *) pathToSequenceOfItem: (ICItem *) item;
@end

@implementation ICCollectionManager

+ (ICCollectionManager *) defaultManager
{
	if (sharedInstance == nil)
	{
		sharedInstance = [[ICCollectionManager alloc] init];
	}
	return sharedInstance;
}

/** override */

- (id) init
{
	self = [super init];

	library = [[NSMutableDictionary alloc] init];

  geneCollection = [[ICCollection alloc] init];
  primerCollection = [[ICCollection alloc] init];
  plasmidCollection = [[ICCollection alloc] init];

	[geneCollection setTitle: @"Genes"];
	[primerCollection setTitle: @"Primers"];
	[plasmidCollection setTitle: @"Plasmids"];

	defaultCollections = [[NSMutableArray alloc] init];
	customCollections = [[NSMutableArray alloc] init];

  [defaultCollections addObject: geneCollection];
  [defaultCollections addObject: primerCollection];
  [defaultCollections addObject: plasmidCollection];

	sequenceReader = [[BCSequenceReader alloc] init];
	sequenceWriter = [[BCSequenceWriter alloc] init];

  return self;
}

- (void) dealloc
{
	DESTROY(geneCollection);
	DESTROY(primerCollection);
	DESTROY(plasmidCollection);
	DESTROY(defaultCollections);
	DESTROY(customCollections);
	DESTROY(library);
	DESTROY(sequenceReader);
	DESTROY(sequenceWriter);
	[super dealloc];
}

/* This method will be used when loading database and when user create new sequence */
- (void) addItem: (ICItem *) item
{
	/* If identifier is duplicated, do nothing */
	if ([library objectForKey: [item identifier]])
	{
		NSLog(@"Warning !! Identifier is duplicated.");
		return;
	}
	/* If there is no identifier, create one */
	if ([item identifier] == nil)
	{
		[item setIdentifier: [self uniqueIdentifier]];
	}
	/* See whether there is a sequence */
	if ([item DNASequence] == nil)
	{
		NSString *path = [self pathToSequenceOfItem: item];
		NSFileManager *fm = [NSFileManager defaultManager];
		BOOL isDir = NO;
		if (([fm fileExistsAtPath: path isDirectory: &isDir]) && isDir == NO)
		{
			NSString *text = [NSString stringWithContentsOfFile: path encoding: NSUTF8StringEncoding error: NULL];
			BCSequenceArray *array = [sequenceReader readFastaFile: text];
			if ([array count] > 0)
			{
				BCSequence *sequence = [array sequenceAtIndex: 0];
				[item setDNASequence: sequence];
			}
			else
			{
				NSAssert1(NO, @"No valid sequence file at %@", path);
			}
		}
	}
	[library setObject: item forKey: [item identifier]];
	switch([item type]) {
		case ICItemGeneType:
			[geneCollection addItem: item];
			break;
		case ICItemPrimerType:
			[primerCollection addItem: item];
			break;
		case ICItemPlasmidType:
			[plasmidCollection addItem: item];
			break;
		default:
			NSAssert1(NO, @"%@: Shouldn't reach here", NSStringFromSelector(_cmd));
	}
}

- (void) removeItem: (ICItem *) item
{
	NSLog(@"Remove item %@ from %@", item, self);

	/* Remove from collections */
	switch ([item type]) {
		case ICItemGeneType:
			[geneCollection removeItem: item];
			break;
		case ICItemPrimerType:
			[primerCollection removeItem: item];
			break;
		case ICItemPlasmidType:
			[primerCollection removeItem: item];
			break;
		default:
			NSAssert1(NO, @"%@: Shouldn't reach here", NSStringFromSelector(_cmd));
	}

	/* Remove sequence file */
	NSString *p = [self pathToSequenceOfItem: item];
	NSFileManager *fm = [NSFileManager defaultManager];
	if ([fm fileExistsAtPath: p] == YES)
	{
		if ([fm removeFileAtPath: p handler: nil] == NO)
		{
			/* File exists, but fails to remove it */
			NSLog(@"Failed to remove sequence file at %@", p);
		}
	}

	/* Remove from library */
	[library removeObjectForKey: [item identifier]];
}

- (void) saveSequenceOfItem: (ICItem *) item
{
	NSString *path = [self pathToSequenceOfItem: item];
	BCSequenceArray *array = [[BCSequenceArray alloc] init];
	[array addSequence: [item DNASequence]];
	if ([[sequenceWriter writeFastaFile: array] writeToFile: path atomically: YES encoding: NSUTF8StringEncoding error: NULL] == NO)
	{
		NSLog(@"Error: cannot save fast file at %@", path);
	}
	DESTROY(array);
}

- (void) organizeItem: (ICItem *) item
{
	/* Find the old collection */
	RETAIN(item);
	if ([[geneCollection allItems] containsObject: item])
	{
		if ([item type] == ICItemGeneType)
			return;
		else
			[geneCollection removeItem: item];
	}
	else if ([[primerCollection allItems] containsObject: item])
	{
		if ([item type] == ICItemPrimerType)
			return;
		else
			[primerCollection removeItem: item];
	}
	else if ([[plasmidCollection allItems] containsObject: item])
	{
		if ([item type] == ICItemPlasmidType)
			return;
		else
			[plasmidCollection removeItem: item];
	}
	else
	{
		NSAssert(NO, @"Item is not in any collection");
	}

	switch([item type]) {
		case ICItemGeneType:
			[geneCollection addItem: item];
			break;
		case ICItemPrimerType:
			[primerCollection addItem: item];
			break;
		case ICItemPlasmidType:
			[plasmidCollection addItem: item];
			break;
		default:
			NSAssert1(NO, @"Should not reach here at %@", NSStringFromSelector(_cmd));
	}
	RELEASE(item);
}

/** accessory */

- (ICCollection *) geneCollection
{
	return geneCollection;
}

- (ICCollection *) primerCollection
{
	return primerCollection;
}

- (ICCollection *) plasmidCollection
{
	return plasmidCollection;
}

- (NSInteger) numberOfDefaultCollections
{
	return [defaultCollections count];
}

- (NSInteger) numberOfCustomCollections
{
	return [customCollections count];
}

- (ICCollection *) defaultCollectionAtIndex: (NSInteger) index
{
	return [defaultCollections objectAtIndex: index];
}

- (ICCollection *) customCollectionAtIndex: (NSInteger) index
{
	return [customCollections objectAtIndex: index];
}

/** The first letter of database path is the version number, from 0-9,A-Z,a-z, totally possible 62 versions */

#define iClonesDatabasePath ([[self pathToDatabase] stringByAppendingPathComponent: @"0Database.xml"])
#define iClonesBackupPath ([[self pathToDatabase] stringByAppendingPathComponent: @"0Database.bak"])

- (void) saveDatabase
{
	/* we need to remove old databse backup before creating new backup. */
	NSFileManager *fm = [NSFileManager defaultManager];
	BOOL isDir = NO;
	NSMutableDictionary *dict = AUTORELEASE([[NSMutableDictionary alloc] init]);
	NSEnumerator *e = nil;
	ICItem *item = nil;

	if ([fm fileExistsAtPath: iClonesBackupPath isDirectory: &isDir] == YES)
	{
		if (isDir == YES)
		{
			NSAssert1(NO, @"Backup is corrupted at %@", iClonesBackupPath);
		}
		else
		{
			if ([fm removeFileAtPath: iClonesBackupPath handler: nil] == NO)
			{
				NSAssert1(NO, @"Cannot remove backup at %@", iClonesBackupPath);
			}
		}
	}

  /* Move current database to backup */
	if (([fm fileExistsAtPath: iClonesDatabasePath] == YES) && ([fm movePath: iClonesDatabasePath toPath: iClonesBackupPath handler: nil] == NO))
	{
		NSAssert1(NO, @"Cannot create database at %@", iClonesBackupPath);
	}

	/* Make NSDictionary to save */
	e = [library objectEnumerator];
	while ((item  = [e nextObject]))
	{
		[dict setObject: [item propertyList] forKey: [item identifier]];
	}

	if ([dict writeToFile: iClonesDatabasePath atomically: YES] == NO)
	{
		NSLog(@"Cannot save database to %@", iClonesDatabasePath);
	}
}

- (void) loadDatabase
{
	NSDictionary *dict = [NSDictionary dictionaryWithContentsOfFile: iClonesDatabasePath];
	NSEnumerator *e = nil;
	NSDictionary *propertyList = nil;
	ICItem *item = nil;
	if (dict != nil)
	{
		e = [dict objectEnumerator];
		while ((propertyList = [e nextObject]))
		{
			item = [[ICItem alloc] initWithPropertyList: propertyList];
			[self addItem: item];
			DESTROY(item);
		}
	}
}

@end

@implementation ICCollectionManager (Private)

- (NSString *) pathToDatabase
{
	BOOL isDir = NO;
	NSString *path = nil;
	NSFileManager *fm = [NSFileManager defaultManager];
	NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
	if ([paths count] > 0)
	{
		path = [[paths objectAtIndex: 0] stringByAppendingPathComponent: iClonesPathComponent];
		if ([fm fileExistsAtPath: path isDirectory: &isDir] == YES)
		{
			if (isDir == YES)
			{
				/* iClones directory exists and is a directory */
				return path;
			}
			else
			{
				/* iClones directory exists but is not a directory */
				NSAssert1(NO, @"iClones cannot accesses its database at %@", path);
				return nil;
			}
		}
		else
		{
			/* iClones directory does not exist. Create one */
			if ([fm createDirectoryAtPath: path attributes: nil] == YES)
			{
				NSString *sequencesPath = [path stringByAppendingPathComponent: iClonesSequencesPathComponent];
				if ([fm createDirectoryAtPath: sequencesPath attributes: nil] == NO)
				{
					NSAssert1(NO, @"Cannot create iClones sequences at %@", sequencesPath);
				}
				return path;
			}
			else
			{
				/* Cannot create iClones path */
				NSAssert1(NO, @"Cannot create iClones directory at %@", path);
				return nil;
			}
		}
	}
	NSAssert(NO, @"Cannot find path for database");
	return nil;
}

- (NSString *) uniqueIdentifier
{
	NSInteger count = [library count];
	NSString *key = nil;
	while(1)
	{
		key = [NSString stringWithFormat: @"%d", count++];
		if (count > 0xFFFFFFFF)
		{
			NSAssert(NO, @"Out of identifier");
			return nil;
		}
		if ([library objectForKey: key] == nil)
		{
			return key;
		}
	}
	NSAssert1(NO, @"%@: Shouldn't reach here", NSStringFromSelector(_cmd));
	return nil;
}

- (NSString *) pathToSequenceOfItem: (ICItem *) item
{
	return [[[[self pathToDatabase] stringByAppendingPathComponent: iClonesSequencesPathComponent] stringByAppendingPathComponent: [item identifier]] stringByAppendingPathExtension: iClonesSequencePathExtension];
}

@end
